using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Conusic.VkApi;

namespace Conusic
{
    public static class VkontakteApi
    {
        /// <summary>
        /// Definition of all api method supported by this class
        /// </summary>
        private static readonly Dictionary<ApiMethodEnum, VkApiMethod> methods =
            new Dictionary<ApiMethodEnum, VkApiMethod>()
                {
                    {
                        // http://vk.com/developers.php?oid=-1&p=friends.get
                        ApiMethodEnum.FriendsGet,
                        new VkApiMethod()
                            {
                                Name = "friends.get",
                                DefaultParam = "fields=uid,first_name,last_name",
                                ReturnType = typeof(VkApi.FriendsGet.response)
                            }
                    },

                                     {
                        // http://vk.com/developers.php?oid=-1&p=audio.search
                        ApiMethodEnum.AudioSearch,
                        new VkApiMethod()
                            {
                                Name = "audio.search",
                                DefaultParam = "sort=2",
                                ReturnType = typeof(VkApi.AudioSearch.response)
                            }
                    },
                    
                  
                };

        /// <summary>
        /// Class log
        /// </summary>

        private static readonly ThreadTube _VkApiThreadTube;

        static VkontakteApi()
        {
            _VkApiThreadTube = new ThreadTube(500);
        }

        public static T RequestApiList<T>(string param = null, ParamAddType addType = ParamAddType.AppendDefault)
        {
            var method = GetMethodByType(typeof(T));
            return RequestApi<T>(method, param, addType);
        }

        private static void CopyStream(Stream from, Stream to)
        {
            int bufSize = 1024, count;
            byte[] buffer = new byte[bufSize];
            count = from.Read(buffer, 0, bufSize);
            while (count > 0)
            {
                to.Write(buffer, 0, count);
                count = from.Read(buffer, 0, bufSize);
            }
        }
        private static T RequestApi<T>(ApiMethodEnum method, string param = null, ParamAddType addType = ParamAddType.AppendDefault)
        {
            var fqpn = GetFullyQualifedParamName(method, param, addType);
            // Proceed query to vk api
            var savedStream = new MemoryStream();
            using (Stream httpStream = RequestApiMethod(GetMethod(method).Name, fqpn))
            {
                // Copy data to local stream for catching error messages

                // .net 4.0 only
                // httpStream.CopyTo(savedStream);
                       
                CopyStream(httpStream, savedStream);
            }

            savedStream.Position = 0;
            savedStream.Position = 0;
            // TODO: Scheme validation
            var xmlReaderSettings = new XmlReaderSettings();
            //            xmlReaderSettings.ValidationType = ValidationType.Schema;
            //            xmlReaderSettings.ValidationEventHandler +=
            //                (sender, args) => log.Error("Validation error: " + args.Message, args.Exception);
            //            xmlReaderSettings.Schemas.Add("", "PhotosGetAlbums.xsd");

            XmlReader xmlStream = XmlReader.Create(savedStream, xmlReaderSettings);

            // Try desirialize xml answer
            var xmlser = new XmlSerializer(typeof(T));

            if (xmlser.CanDeserialize(xmlStream))
            {
                var result = (T)xmlser.Deserialize(xmlStream);
                return result;
            }

            try
            {
                // If not, try throw parsed APi Exception
                TryThrowVkApiException<T>(xmlStream);

                // If not, throw exception with full xml answer in message body
                savedStream.Position = 0;
                var text = (new StreamReader(savedStream)).ReadToEnd();
                throw new ParseException(String.Format("Can't parse xml vkapi answer: {0}", text));
            }
            catch (Exception e)
            {
                throw;
            }
        }

        private static void TryThrowVkApiException<T>(XmlReader xmlStream2)
        {
            var xmlErrorSer = new XmlSerializer(typeof(VkApi.Error.error));
            if (xmlErrorSer.CanDeserialize(xmlStream2))
            {
                var error = (VkApi.Error.error)xmlErrorSer.Deserialize(xmlStream2);
                throw new VkApiException(error.error_code, error.error_msg);
            }
        }

        private static string GetFullyQualifedParamName(ApiMethodEnum method, string param, ParamAddType addType)
        {
            string fqpn;
            switch (addType)
            {
                case ParamAddType.NoDefault:
                    fqpn = param;
                    break;
                case ParamAddType.AppendDefault:
                    if (!String.IsNullOrEmpty(param))
                    {
                        fqpn = String.Format("{0}&{1}", GetMethod(method).DefaultParam, param);
                    }
                    else
                    {
                        fqpn = GetMethod(method).DefaultParam;
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException("addType");
            }
            return fqpn;
        }

        private static VkApiMethod GetMethod(ApiMethodEnum method)
        {
            if (methods.ContainsKey(method))
            {
                return methods[method];
            }
            throw new ArgumentOutOfRangeException("method");
        }

        /// <summary>
        /// Return Method class by given return type
        /// </summary>
        /// <param name="type">expected return type</param>
        /// <returns>VkApiMethod instanse or null</returns>
        private static ApiMethodEnum GetMethodByType(Type type)
        {
            var result = new List<ApiMethodEnum>();
            foreach (var vkApiMethod in methods)
            {
                if(vkApiMethod.Value.ReturnType == type)
                    result.Add(vkApiMethod.Key);
            }
            if (result.Count != 1)
            {
                throw new ArgumentException("Wrong type "+ type);
            }
            return result[0];
        }

        /// <summary>
        /// Procees query to vk.com servers and return answer stream pointer
        /// </summary>
        /// <param name="method">Method for performing operation</param>
        /// <param name="param">Param string</param>
        /// <returns>non-seeked stream</returns>
        internal static Stream RequestApiMethod(string method, string param)
        {
            //Wait for Vkontakte access tube
            _VkApiThreadTube.Wait();
            const string apiUrl = "https://api.vk.com/method/";

            var url = String.Format("{0}{1}.xml?{2}&access_token={3}", apiUrl, method, param, VkontakteAuthorization.GetToken());


            var request = (HttpWebRequest)WebRequest.Create(url);
            Config.Instance.SetProxyConfiguration(request);

            try
            {
                var response = request.GetResponse();
                var responseStream = response.GetResponseStream();
                return (responseStream);
            }
            catch (Exception e)
            {
                throw;
            }
        }
    }

}

namespace Conusic.VkApi
{
    public enum ParamAddType
    {
        NoDefault,
        AppendDefault
    }

    public enum ApiMethodEnum
    {
        FriendsGet,
        FriendsGetLists,
        WallGet,
        PhotosGetAlbums,
        PhotosGet,
        PhotosGetAllComments,
        WallGetComments,
        MessagesGet,
        AudioSearch
    }
}
